#!/usr/local/bin/python3

import os, io, json
import sys
import time
import functools
import importlib
import tempfile
import multiprocessing
import requests
import errno
import subprocess
import threading
import ssl

from urllib import request, parse
from omegaconf.dictconfig import DictConfig
from contextlib import contextmanager
from frepai.utils.error import errmsg

ssl._create_default_https_context = ssl._create_unverified_context

DEBUG = False
TEMPDIR = '/tmp/'
APPID = '1301930378'


class SingletonType(type):
    _instance_lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with SingletonType._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
        return cls._instance


def frep_blockprint(func):
    @functools.wraps(func)
    def decorator(*args, **kwargs):
        sys.stdout = open(os.devnull, 'w')
        sys.stderr = open(os.devnull, 'w')
        results = func(*args, **kwargs)
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__
        return results
    return decorator


def frep_timeit(handler):
    def decorator(func):
        @functools.wraps(func)
        def _timed(*args, **kwargs):
            ts = time.time()
            result = func(*args, **kwargs)
            te = time.time()
            if handler:
                handler('{} took {:.3f}'.format(func.__name__, (te - ts) * 1000))
            return result
        return _timed
    return decorator


def frep_load_class(impstr):
    if DEBUG:
        print(f'load class: {impstr}')
    module_name, class_name = impstr.rsplit('.', 1)
    module_ = importlib.import_module(module_name)
    return getattr(module_, class_name)


def frep_convert_dictkeys(x, uppercase=True):
    fcvt = str.upper if uppercase else str.lower
    if isinstance(x, (dict, DictConfig)):
        return dict((fcvt(k), frep_convert_dictkeys(v, uppercase)) for k, v in x.items())
    return x


def frep_data(x, path='/frepai/data/tmp'):
    if x.startswith('http') or x.startswith('ftp'):
        mkdir_p(path)
        x = parse.quote(x, safe=':/?-=')
        r = request.urlretrieve(x, os.path.join(path, os.path.basename(x)))
        x = r[0]
    elif x.startswith('oss://'):
        raise NotImplementedError('weight schema: oss')
    elif x.startswith('file://'):
        x = x[7:]
    if DEBUG:
        print(x)
    return x


def frep_report_result(key, data):
    api = 'http://{}/frepai/private/pushmsg?key={}'.format('0.0.0.0:9119', key)
    if isinstance(data, str):
        requests.post(api, data=data)
    else:
        requests.post(api, json=data)


@contextmanager
def frep_subprocess(func, *args):
    queue = multiprocessing.Queue()

    def _target(queue, *args):
        try:
            with tempfile.TemporaryDirectory() as tmp_dir:
                global TEMPDIR
                TEMPDIR = tmp_dir
                queue.put(func(*args))
        except Exception as err:
            queue.put(errmsg(90099, err))

    proc = multiprocessing.Process(target=_target, args=(queue, *args))
    proc.start()
    yield queue
    proc.join()


def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise


def frep_oss_client(access_key=None, secret_key=None, region='ap-beijing'):
    if access_key is None:
        access_key = os.environ.get('MINIO_ACCESS_KEY', 'AKIDV7XjgOr42nMhneGdmiPs66rNioeFafeT')
    if secret_key is None:
        secret_key = os.environ.get('MINIO_SECRET_KEY', 'd190cxQk0CHCtLXjhQt65tUr2yf7KI1V')

    print(access_key, secret_key)

    from qcloud_cos import CosConfig
    from qcloud_cos import CosS3Client
    config = CosConfig(Region=region, SecretId=access_key, SecretKey=secret_key, Token=None, Scheme='https')
    return CosS3Client(config)


def frep_object_put(client, local_path, prefix_map=None, bucket_name=None):
    if bucket_name is None:
        bucket_name = f'frepai-{APPID}'

    result = []

    def _upload_file(local_file):
        if not os.path.isfile(local_file):
            result.append({'error': local_file})
            return
        if prefix_map and isinstance(prefix_map, list):
            lprefix = prefix_map[0].rstrip(os.path.sep)
            rprefix = prefix_map[1].strip(os.path.sep)
            remote_file = local_file.replace(lprefix, rprefix, 1)
        else:
            remote_file = local_file.lstrip(os.path.sep)

        file_size = os.stat(local_file).st_size
        with open(local_file, 'rb') as file_data:
            btime = time.time()
            etag = client.put_object(
                    Bucket=bucket_name,
                    Body=file_data,
                    Key=remote_file)
            etime = time.time()
            result.append({
                'etag': etag['ETag'].strip('"'),
                'bucket': 'frepai',
                'object': remote_file,
                'size': file_size,
                'time': [btime, etime]})

    if os.path.isdir(local_path):
        for root, directories, files in os.walk(local_path):
            for filename in files:
                _upload_file(os.path.join(root, filename))
    else:
        _upload_file(local_path)

    return result


def frep_object_put_jsonconfig(client, data, path, bucket_name=None):
    if bucket_name is None:
        bucket_name = f'frepai-{APPID}'

    if isinstance(data, (dict, list)):
        data = json.dumps(data, ensure_ascii=False, indent=4)
    with io.BytesIO(data.encode()) as bio:
        if path[0] == '/':
            path = path[1:]
        etag = client.put_object(
                Bucket=bucket_name,
                Body=bio,
                Key=path)
        return etag['ETag'].strip('"')


def frep_object_remove(client, remote_path, bucket_name=None):
    if bucket_name is None:
        bucket_name = f'frepai-{APPID}'
    result = []
    marker = ""
    while True:
        response = client.list_objects(
                Bucket=bucket_name,
                Prefix=remote_path,
                MaxKeys=100,
                Marker=marker)
        if 'Contents' in response:
            for obj in response['Contents']:
                client.delete_object(
                        Bucket=bucket_name,
                        Key=obj['Key'])
                result.append({
                    'etag': obj['ETag'].strip('"'),
                    'bucket': 'frepai',
                    'object': obj['Key'],
                    'size': obj['Size']})
        if response['IsTruncated'] == 'false':
            break
        marker = response['NextMarker']

    return result


def frep_run_shell(cmd):
    try:
        output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
    except Exception as err:
        output = err.output
    return output.decode()
